/* --------------------------------------------------------
 * Copyright (c) aeky , Inc.  All rights reserved.
 * --------------------------------------------------------
 */
package com.aeky.client.mvc;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.History;

/**
 * TODO
 * 
 * @author aeky
 */
public class Dispatcher extends BaseObservable {
    /**
     * Fires before an event is dispatched.
     */
    public static final EventType BeforeDispatch = new EventType();

    /**
     * Fires after an event has been dispatched.
     */
    public static final EventType AfterDispatch = new EventType();

    private static Dispatcher instance;

    private static boolean historyEnabled = true;

    /**
     * Forwards an application event to the dispatcher.
     * 
     * @param event the application event
     */
    public static void forwardEvent(AppEvent event) {
        get().dispatch(event);
    }

    /**
     * Creates and forwards an application event to the dispatcher.
     * 
     * @param eventType the application event type
     */
    public static void forwardEvent(EventType eventType) {
        get().dispatch(eventType);
    }

    /**
     * Creates and forwards an application event to the dispatcher.
     * 
     * @param eventType the application event type
     * @param data the event data
     */
    public static void forwardEvent(EventType eventType, Object data) {
        get().dispatch(new AppEvent(eventType, data));
    }

    /**
     * Creates and forwards an application event to the dispatcher.
     * 
     * @param eventType the application event type
     * @param data the event data
     * @param historyEvent true to mark event as a history event
     */
    public static void forwardEvent(EventType eventType, Object data, boolean historyEvent) {
        AppEvent ae = new AppEvent(eventType, data);
        ae.setHistoryEvent(historyEvent);
        get().dispatch(ae);
    }

    /**
     * Returns the singleton instance.
     * 
     * @return the dispatcher
     */
    public static Dispatcher get() {
        if (instance == null) {
            instance = new Dispatcher();
        }
        return instance;
    }

    private Map<String, AppEvent> history;

    private List<Controller> controllers;
    private Boolean supportsHistory = null;

    private Dispatcher() {
        controllers = new ArrayList<Controller>();
        history = new HashMap<String, AppEvent>();
        if (supportsHistory()) {
            History.addValueChangeHandler(new ValueChangeHandler<String>() {
                public void onValueChange(ValueChangeEvent<String> event) {
                    String historyToken = event.getValue();
                    if (history.containsKey(historyToken)) {
                        dispatch(history.get(historyToken), false);
                    }
                }
            });
        }
    }

    /**
     * Adds a controller.
     * 
     * @param controller the controller to be added
     */
    public void addController(Controller controller) {
        if (!controllers.contains(controller)) {
            controllers.add(controller);
        }
    }

    /**
     * Adds a listener to receive dispatch events.
     * 
     * @param listener the listener to add
     */
    public void addDispatcherListener(DispatcherListener listener) {
        addListener(BeforeDispatch, listener);
        addListener(AfterDispatch, listener);
    }

    /**
     * The dispatcher will query its controllers and pass the application event
     * to any controllers that can handle the particular event type.
     * 
     * @param event the application event
     */
    public void dispatch(AppEvent event) {
        dispatch(event, event.isHistoryEvent());
    }

    /**
     * The dispatcher will query its controllers and pass the application event
     * to controllers that can handle the particular event type.
     * 
     * @param type the event type
     */
    public void dispatch(EventType type) {
        dispatch(new AppEvent(type));
    }

    /**
     * The dispatcher will query its controllers and pass the application event
     * to controllers that can handle the particular event type.
     * 
     * @param type the event type
     * @param data the app event data
     */
    public void dispatch(EventType type, Object data) {
        dispatch(new AppEvent(type, data));
    }

    /**
     * Returns all controllers.
     * 
     * @return the list of controllers
     */
    public List<Controller> getControllers() {
        return controllers;
    }

    /**
     * Returns the dispatcher's history cache.
     * 
     * @return the history
     */
    public Map<String, AppEvent> getHistory() {
        return history;
    }

    /**
     * Removes a controller.
     * 
     * @param controller the controller to be removed
     */
    public void removeController(Controller controller) {
        controllers.remove(controller);
    }

    /**
     * Removes a previously added listener.
     * 
     * @param listener the listener to be removed
     */
    public void removeDispatcherListener(DispatcherListener listener) {
        removeListener(BeforeDispatch, listener);
        removeListener(AfterDispatch, listener);
    }

    private void dispatch(AppEvent event, boolean createhistory) {
        MvcEvent e = new MvcEvent(this, event);
        e.setAppEvent(event);
        if (fireEvent(BeforeDispatch, e)) {
            List<Controller> copy = new ArrayList<Controller>(controllers);
            for (Controller controller : copy) {
                if (controller.canHandle(event)) {
                    if (!controller.initialized) {
                        controller.initialized = true;
                        controller.initialize();
                    }
                    controller.handleEvent(event);
                }
            }
            fireEvent(AfterDispatch, e);
        }
        if (createhistory && event.isHistoryEvent()) {
            String token = event.getToken();
            if (token == null) {
                token = "" + new Date().getTime();
            }
            history.put(token, event);
            if (supportsHistory()) {
                History.newItem(token, false);
            }
        }
    }

    private boolean supportsHistory() {
        // if (supportsHistory == null) {
        // // supportsHistory = historyEnabled && GWT.isClient()
        // // && (XDOM.getElementById("__gwt_historyFrame") != null ||
        // // !(GXT.isIE6 || GXT.isIE7));
        // }
        // return supportsHistory;
        return false;
    }
}
